const path = require("path");

function debounce(func, wait, options = {}) {
  let timeout,
    result,
    lastCall = 0,
    lastInvoke = 0;
  const { leading, maxWait } = options;
  const trailing = options.trailing !== false;

  function invokeFunc(time, context, args) {
    lastInvoke = time;
    return func.apply(context, args);
  }

  function shouldInvoke(time) {
    const timeSinceLastCall = time - lastCall;
    const timeSinceLastInvoke = time - lastInvoke;
    return (
      !lastCall ||
      timeSinceLastCall >= wait ||
      timeSinceLastCall <= 0 ||
      (maxWait && timeSinceLastInvoke >= maxWait)
    );
  }

  function timerExpired() {
    const time = Date.now();
    if (shouldInvoke(time)) return trailingEdge(time);
    timeout = setTimeout(timerExpired, remainingWait(time));
  }

  function remainingWait(time) {
    const timeSinceLastCall = time - lastCall;
    const timeSinceLastInvoke = time - lastInvoke;
    const timeWaiting = wait - timeSinceLastCall;
    return Math.max(
      maxWait
        ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
        : timeWaiting,
      0
    );
  }

  function trailingEdge(time) {
    timeout = undefined;
    if (trailing) return invokeFunc(time, this, args);
    return result;
  }

  function debounced() {
    const time = Date.now();
    const isInvoking = shouldInvoke(time);
    const args = arguments;

    lastCall = time;
    if (isInvoking) {
      if (!timeout) {
        return leadingEdge(this, args);
      }
      if (maxWait) {
        timeout = setTimeout(timerExpired, wait);
        return invokeFunc(lastCall, this, args);
      }
    }
    if (!timeout) timeout = setTimeout(timerExpired, wait);
    return result;
  }

  function leadingEdge(context, args) {
    timeout = setTimeout(timerExpired, wait);
    return leading ? invokeFunc(lastCall, context, args) : result;
  }

  debounced.cancel = function () {
    clearTimeout(timeout);
    lastCall = lastInvoke = 0;
  };

  return debounced;
}

function throttle(func, wait, options = {}) {
  let context,
    args,
    result,
    timeout = null,
    previous = 0;
  const later = function () {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  return function () {
    const now = Date.now();
    if (!previous && options.leading === false) previous = now;
    const remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
}

// updateProgress seems to be okay.
// Minor changes: added function comments to explain what's happening.

function updateProgress(updateStatusFunc, message, callback = () => {}) {
  // Handle progress messages.
  if (message.type === "progress") {
    updateStatusFunc(
      {
        taskid: message.taskid,
        status: 1,
        code: message.code,
        progress: message.progress,
      },
      callback
    );
  }
  // Handle ended messages.
  if (message.type === "ended") {
    const status = message.code === 0 ? 2 : 3;
    const progress = message.code === 0 ? 100 : 0;
    updateStatusFunc(
      {
        taskid: message.taskid,
        status: status,
        code: message.code,
        progress: progress,
      },
      callback
    );
  }
}

// Helper function for error handling.
function errorHandling(type, taskid) {
  return (err) => {
    if (err) console.error(`updateStatusFunc ${type} err`, taskid, err);
  };
}

const throttledUpdateProgress = throttle(updateProgress, 100, {
  trailing: true,
  leading: true,
});

async function initRowData({
  eventData,
  getRowFunc,
  insertItemFunc,
  updateStatusFunc,
  isDownloadMedia,
  isUploadMedia,
}) {
  const rowData = await getRowFunc(eventData.taskid);
  if (!rowData) {
    await new Promise((resolve, reject) => {
      let newItem;
      if (isDownloadMedia) {
        newItem = {
          taskid: eventData.taskid,
          userid: eventData.uidStr,
          status: 0,
          fileName: eventData.md5Str,
          fileSize: "",
          filePath: eventData.szLocalFilename,
          fileOwner: eventData.szJsonString.owner,
          fileRemotePath: "",
          fileMimeType: "",
          fileType: "",
          progress: 0,
          localDirectory: path.join(__dirname, "../../img/"),
          downloadDate: Date.now(),
          completedDate: Date.now(),
          groupDeviceId: eventData.groupDeviceId,
          deviceId: eventData.deviceId,
          hostname: "",
          syKeyDid: eventData.systemKeyObj.did,
          syKeyInitString: eventData.systemKeyObj.initString,
          syKeyApiLicenses: "",
          syKeyCrcKey: "",
          playload: {
            albumid: eventData.albumid,
            md5Str: eventData.md5Str,
          },
          code: 0,
        };
      }
      if (isUploadMedia) {
        newItem = {
          taskid: eventData.taskid,
          userid: eventData.uidStr,
          status: 0,
          fileName: eventData.szJsonString.showname,
          fileSize: eventData.szJsonString.fsize,
          filePath: eventData.szLocalFilename,
          fileOwner: eventData.szJsonString.owner,
          fileRemotePath: eventData.szJsonString.showname,
          fileMimeType: "",
          fileType: eventData.szJsonString.ftype,
          progress: 0,
          localDirectory: eventData.szLocalFilename,
          uploadDate: Date.now(),
          completedDate: Date.now(),
          groupDeviceId: eventData.groupDeviceId,
          syKeyDid: eventData.systemKeyObj.did,
          syKeyInitString: eventData.systemKeyObj.initString,
          syKeyApiLicenses: "",
          syKeyCrcKey: "",
          playload: {
            albumid: eventData.szJsonString.albumid,
            shootingtime: eventData.szJsonString.shootingtime,
            ctime: eventData.szJsonString.ctime,
          },
          code: 0,
        };
      }

      insertItemFunc(newItem, (err) => {
        if (err) {
          return reject(err);
        }
        resolve();
      });
    });
  } else {
    await new Promise((resolve, reject) => {
      console.log("Data exists", eventData.taskid, rowData);
      const message = {
        type: "progress",
        taskid: eventData.taskid,
        code: 0,
        progress: 0,
      };
      updateProgress(updateStatusFunc, message, (err) => {
        !err ? resolve() : reject(err);
      });
    });
  }
}

// 创建一个Map来存储id和节流函数的对应关系
const throttledMap = new Map();

function getThrottledUpdate(id, updateProgress, duration = 100) {
  // 如果Map中已经存在这个id的节流函数，就直接返回
  if (throttledMap.has(id)) {
    return throttledMap.get(id);
  }

  // 如果Map中不存在这个id的节流函数，就创建一个新的，并存入Map
  const throttledUpdate = throttle(
    (...reset) => updateProgress(...reset),
    duration,
    {
      leading: true,
      trailing: true,
    }
  );
  throttledMap.set(id, throttledUpdate);

  return throttledUpdate;
}

module.exports = {
  debounce,
  throttle,
  throttledMap,
  getThrottledUpdate,
  initRowData,
  throttledUpdateProgress,
  updateProgress,
};
